package simulator.software;

import java.util.List;
import java.util.Vector;

import simulator.hardware.Computer;
import simulator.hardware.Processor;
import simulator.software.scheduling.RoundRobinScheduler;
import simulator.software.scheduling.Scheduler;

/**
 * The <code>Kernel</code> represents the core of an operating system running on
 * a <code>Computer</code>. Serves as the modular hub for various operating
 * system activities, such a process scheduling, memory management, and file
 * system management.
 * 
 * As a <code>ProcessorRunnable</code>, the <code>Kernel</code> must be loaded
 * onto a processor in order to progress in the simulation.
 * 
 * @author Kyle Loomis
 * @author David LaPointe
 * 
 */
public class Kernel implements ProcessorRunnable
{
   protected Computer computer = null;
   protected int cyclesPerQuantum;
   protected List<Process> processes = new Vector<Process>();
   protected Scheduler scheduler = null;
   protected ProcessState state = null;
   protected String statusDescriptor = "Ready";
   protected int cycles;
   protected int idleCycles;
   protected int interruptCycles;
   protected int kernelCycles;

   /**
    * Creates a <code>Kernel</code> on the given <code>.Computer</code>
    * 
    * @param computer
    *           the <code>Computer</code> that owns this <code>Kernel</code>.
    */
   public Kernel(Computer computer)
   {
      if (computer != null)
      {
         this.computer = computer;
         this.computer.setKernel(this);
      }
      else
      {
         throw new IllegalArgumentException("No computer provided for kernel.");
      }

      scheduler = new RoundRobinScheduler();
   }

   /**
    * Gets the process scheduling algorithm currently in use by this
    * <code>Kernel</code>.
    * 
    * @return the process scheduling algorithm currently in use by this
    *         <code>Kernel</code>.
    */
   public Scheduler getScheduler()
   {
      return this.scheduler;
   }

   /**
    * Sets the process scheduling algorithm to use when choosing processes to
    * run
    * 
    * @param scheduler
    *           the process scheduling algorithm to use when choosing processes
    *           to run
    */
   public void setScheduler(Scheduler scheduler)
   {
      this.scheduler = scheduler;
   }

   /**
    * Gets the <code>Computer</code> associated with this <code>Kernel</code>.
    * 
    * @return the computer associated with this <code>Kernel</code>.
    */
   public Computer getComputer()
   {
      return computer;
   }

   /**
    * Gets the number of cycles (steps) allowed to each process before
    * preemption.
    * 
    * @return the number of cycles (steps) allowed to each process
    */
   public int getCyclesPerQuantum()
   {
      return cyclesPerQuantum;
   }

   /**
    * Gets the process table of this <code>Kernel</code> as a <code>List</code>,
    * ordered by creation time from oldest to newest.
    * 
    * @return a <code>List</code> that represents the process table of this
    *         <code>Kernel</code>.
    */
   public List<Process> getProcesses()
   {
      return processes;
   }

   /**
    * Sets the number of cycles (steps) a process may run before it is
    * preempted.
    * 
    * @param cyclesPerQuantum
    *           the number of cycles (steps) a process may run before it is
    *           preempted.
    */
   public void setCyclesPerQuantum(int cyclesPerQuantum)
   {
      if (cyclesPerQuantum > 0)
      {
         this.cyclesPerQuantum = cyclesPerQuantum;
      }
      else
      {
         throw new IllegalArgumentException(
               "Cycles per quantum must be greater than 0.");
      }
   }

   /**
    * Adds the specified process to this <code>Kernel</code>'s process table.
    * 
    * @param p
    *           the process to add to the process table.
    */
   public void addProcess(Process p)
   {
      processes.add(p);
      p.setComputer(computer);
   }

   public void step()
   {
      setStatusDescriptor("Scheduling process...");
      scheduleProcess();
      kernelCycles++;
      computer.enableInterrupts();
   }

   private void scheduleProcess()
   {
      System.out.println("Scheduling process...");

      Process p = scheduler.nextProcess(processes);
      cycles++;

      if (p != null)
      {
         Processor processor = computer.getProcessors().get(0);
         setState(ProcessState.READY);
         p.setState(ProcessState.RUNNING);
         computer.clearClockInterrupts();
         Interrupt i = new ClockInterrupt(computer.getCurrentStep()
               + getCyclesPerQuantum());
         computer.addInterrupt(i);
         processor.load(p);
         System.out.println("...loading " + p.getName());
      }
      else
      {
         System.out.println("No ready process, going idle!");
         setStatusDescriptor("Idling...");
         idleCycles++;
      }
   }

   /**
    * Handles the specified interrupt.
    * 
    * @param i
    *           the interrupt to handle
    */
   public void handleInterrupt(Interrupt i)
   {
      if (i instanceof ClockInterrupt)
      {
         setStatusDescriptor("Handling clock interrupt...");

         Processor p = computer.getProcessors().get(0);

         ProcessorRunnable process = p.getRunningProcess();

         if (process != null && process != this)
         {
            Process pr = (Process) process;
            pr.setState(ProcessState.READY);
         }

         scheduleProcess();
      }
      else if (i instanceof IOInterrupt)
      {
         setStatusDescriptor("Handling I/O interrupt...");

         IOInterrupt ioInterrupt = (IOInterrupt) i;

         Process p = ioInterrupt.getProcess();
         p.setState(ProcessState.READY);

         System.out.println("I/O interrupt received...waking " + p.getName());
      }

      interruptCycles++;
   }

   public ProcessState getState()
   {
      return state;
   }

   /**
    * Sets to the kernel's internal state to the specified enumeration value.
    * 
    * @param state
    *           the state to put the Kernel in.
    */
   public void setState(ProcessState state)
   {
      this.state = state;
   }

   public String getStatusDescriptor()
   {
      return statusDescriptor;
   }

   /**
    * Sets the description of the current state of this kernel.
    * 
    * @param descriptor
    *           a brief description of the current state of this kernel.
    */
   public void setStatusDescriptor(String descriptor)
   {
      statusDescriptor = descriptor;
   }

   /**
    * Gets the number of cycles (steps) that this kernel has spent running,
    * including idle cycles.
    * 
    * @return the number of cycles (steps) spent running
    */
   public int getCyclesRunning()
   {
      return cycles;
   }

   /**
    * Gets the number of cycles (steps) that this kernel has spent idling.
    * 
    * @return the number of cycles (steps) that this kernel has spent idling.
    */
   public int getIdleCycles()
   {
      return idleCycles;
   }

   /**
    * Gets the number of cycles (steps) that this kernel has spent handling
    * interrupts.
    * 
    * @return the number of cycles (steps) that this kernel has spent handling
    *         interrupts.
    */
   public int getInterruptCycles()
   {
      return interruptCycles;
   }

   /**
    * Gets the number of cycles (steps) that this kernel has spent doing work.
    * 
    * @return the number of cycles (steps) that this kernel has spent doing work
    */
   public int getKernelCycles()
   {
      return kernelCycles;
   }
}